/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011, Red Hat, Inc. and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.mobicents.slee.container.deployment.profile;

import java.util.Map;

import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;

import javax.slee.SLEEException;
import javax.slee.profile.ReadOnlyProfileException;

import org.apache.log4j.Logger;
import org.mobicents.slee.container.component.ClassPool;
import org.mobicents.slee.container.component.profile.ProfileSpecificationComponent;
import org.mobicents.slee.container.component.profile.ProfileSpecificationDescriptor;
import org.mobicents.slee.container.deployment.ClassUtils;
import org.mobicents.slee.container.profile.AbstractProfileCmpSlee10Wrapper;

/**
 * 
 * The Profile CMP Slee 1.0 Wrapper is an object that implements the Profile CMP
 * Interface, wrapping the SLEE 1.1 real profile concrete object in a SLEE 1.0
 * compatible interface. This class generates such objects.
 * 
 * @author martins
 * 
 */
public class ProfileCmpSlee10WrapperClassGenerator {

	private static final Logger logger = Logger.getLogger(ProfileCmpSlee10WrapperClassGenerator.class);
	
	/**
	 * the component to process
	 */
	private final ProfileSpecificationComponent component;

	/**
	 * 
	 * @param component
	 */
	public ProfileCmpSlee10WrapperClassGenerator(ProfileSpecificationComponent component) {
		this.component = component;		
	}

	/**
	 * 
	 * @throws Exception
	 */
	public void generateClass() throws Exception {
		
		ProfileSpecificationDescriptor descriptor = component.getDescriptor();
		ClassPool pool = component.getClassPool();
				
		String profileCmpInterfaceName = descriptor.getProfileCMPInterface().getProfileCmpInterfaceName();
		String wrapperClassName =  profileCmpInterfaceName + "Slee10Wrapper" ;
		CtClass profileCmpInterface = null;
		CtClass wrapperClass = null;
		try {
			profileCmpInterface = pool.get(profileCmpInterfaceName);
			wrapperClass = pool.makeClass(wrapperClassName);
			wrapperClass.setSuperclass(pool.get(AbstractProfileCmpSlee10Wrapper.class.getName()));
			wrapperClass.addInterface(profileCmpInterface);			
		} catch (Exception e) {
			throw new SLEEException(e.getMessage(),e);
		}
		
		// implement only methods from profile cmp and management interface that are also on user defined local object interface
		Map<String, CtMethod> methodsToImplement = ClassUtils.getInterfaceMethodsFromInterface(profileCmpInterface);
		
	    // implement methods delegating to an object casted to the profile cmp concrete class generated by SLEE
	    String profileCmpConcreteClassName = component.getProfileCmpConcreteClass().getName();
	    for (CtMethod ctMethod : methodsToImplement.values()) {
			implementMethod(wrapperClass, ctMethod,"(("+profileCmpConcreteClassName+")profileObject.getProfileConcrete())");
		}
		// generate class file
		try {
			wrapperClass.writeFile(component.getDeploymentDir().getAbsolutePath());			
		}
		catch (Exception e) {
			throw new SLEEException(e.getMessage(), e);
		}
		finally {
			wrapperClass.defrost();
		}
		// load it and store it in the component
		try {			
			component.setProfileCmpSlee10WrapperClass(component.getClassLoader().loadClass(wrapperClassName));
		}
		catch (ClassNotFoundException e) {
			throw new SLEEException(e.getMessage(),e);
		}
	}

	private void implementMethod(CtClass concreteClass, CtMethod method, String interceptorAccess) throws Exception {
		// copy method to concrete class
		CtMethod newMethod = CtNewMethod.copy( method, concreteClass, null );
		// generate body
		String returnStatement = method.getReturnType().equals(CtClass.voidType) ? "" : "return ($r)";
		String body=
			"{ " 
			+ "	try {"
			+ 		returnStatement + interceptorAccess +'.'+ method.getName()+"($$);" 
			+ "	} catch ("+ReadOnlyProfileException.class.getName()+" e) {"
			+ "		throw new "+UnsupportedOperationException.class.getName()+"(e.getMessage(),e);" 
			+ "	}"
			+ "}";
		if(logger.isTraceEnabled())
		{
			logger.trace("Instrumented method, name:"+method.getName()+", with body:\n"+body);
		}
		newMethod.setBody(body);
		// add to concrete class  
		concreteClass.addMethod(newMethod);
	}
}
